package org.eden.heartbeat.core.persistence;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eden.heartbeat.core.configuration.CoreApplicationConfiguration;
import org.eden.heartbeat.core.exception.PersistenceException;
import org.eden.heartbeat.core.exception.PersistenceRuntimeException;
import org.eden.heartbeat.core.persistence.impl.ConfigurationDAOImpl;
import org.eden.heartbeat.core.persistence.impl.RequestDAOImpl;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Simple implementation of a persistence manager. It maintains the database connection state and keeps track of the
 * existing DAOs to perform cleanups (if needed) when the shutdown command is invoked.
 *
 * @author trsimoes
 * @version $Revision:$
 */
public class SimplePersistenceManager {

    private static Log LOG = LogFactory.getLog(SimplePersistenceManager.class);

    private static SimplePersistenceManager ourInstance = new SimplePersistenceManager();

    private Connection connection;

    private List<AbstractDAO> registeredDAOs;

    private Map<Class, Class> configuredDAOInterfaces;

    public static SimplePersistenceManager getInstance() {
        return ourInstance;
    }

    private SimplePersistenceManager() {
        try {
            // check for driver
            Class.forName(CoreApplicationConfiguration.databaseDriver).newInstance();

            // create new connection
            this.connection = DriverManager.getConnection(CoreApplicationConfiguration.databaseUrl,
                    CoreApplicationConfiguration.databaseUsername, CoreApplicationConfiguration.databasePassword);

            this.registeredDAOs = new ArrayList<AbstractDAO>();

            // declare DAO interfaces and its implementations
            this.configuredDAOInterfaces = new HashMap<Class, Class>();
            this.configuredDAOInterfaces.put(RequestDAO.class, RequestDAOImpl.class);
            this.configuredDAOInterfaces.put(ConfigurationDAO.class, ConfigurationDAOImpl.class);

        } catch (Throwable e) {
            throw new PersistenceRuntimeException(e);
        }
    }

    /**
     * Closes the connection and calls the shutdown routine of any registered DAO.
     *
     * @throws PersistenceException thrown when an error occurs while communicating with the database.
     */
    public void close() throws PersistenceException {

        // handle DAOs
        if (!this.registeredDAOs.isEmpty()) {
            for (AbstractDAO dao : this.registeredDAOs) {
                try {
                    dao.shutdown();
                } catch (SQLException e) {
                    if (LOG.isErrorEnabled()) {
                        LOG.error("error shutting down dao " + dao.toString(), e);
                    }
                }
            }
        }

        // close connection
        if (this.connection != null) {
            try {
                this.connection.close();
            } catch (SQLException e) {
                throw new PersistenceException(e);
            }
        }
    }

    /**
     * Registers a new DAO.
     *
     * @param dao the DAO to be registered
     * @param <T> the DAO class
     */
    public <T extends AbstractDAO> void registerDAO(T dao) {
        this.registeredDAOs.add(dao);
    }

    /**
     * Gets the connection to the database.
     *
     * @return database connection.
     */
    public Connection getConnection() {
        return this.connection;
    }

    /**
     * Gets an instance for a given DAO class. If the DAO type is already registered, the existing instance will be
     * returned. If the DAO type was not yet registered, a new instance will be created and registered.
     *
     * @param daoClass the DAO class to be retrieved
     * @param <T> the DAO class type
     * @return the DAO instance
     * @throws PersistenceRuntimeException thrown when a new instance of the DAO could not be created.
     */
    public <T> T getDaoInstance(Class<T> daoClass) throws PersistenceRuntimeException {
        T dao = null;

        for (AbstractDAO registeredDao : this.registeredDAOs) {
            if (registeredDao.getClass().equals(daoClass)) {
                dao = daoClass.cast(registeredDao);
                break;
            }
        }

        if (dao == null) {
            try {
                Class daoImpl = this.configuredDAOInterfaces.get(daoClass);
                dao = daoClass.cast(daoImpl.newInstance());
            } catch (Throwable e) {
                throw new PersistenceRuntimeException(e);
            }
        }

        return dao;
    }
}
